﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Threading;
using System.Windows.Forms;
using DevExpress.XtraEditors.DXErrorProvider;
using Loki.Commands;
using Loki.Utils;

namespace Loki.UI.Win
{
    public class Databinder : LokiObject, IDisposable
    {
        private Dictionary<object, List<Func<object, SendOrPostCallback>>> _BindingCallbacks = new Dictionary<object, List<Func<object, SendOrPostCallback>>>();
        private HashSet<DXValidationProvider> _Validators = new HashSet<DXValidationProvider>();
        private void ClearBindings()
        {
            _BindingCallbacks.Clear();
        }

        public void ResetBindings()
        {
            foreach (var item in _BindingCallbacks)
            {
                foreach (var callBack in item.Value)
                {
                    UIContext.ProtectUIDispatcher(callBack(item.Key));
                }
            }
        }

        public void AddValidator(DXValidationProvider P_Validator)
        {
            foreach (Control L_Control in _BindingCallbacks.Keys)
            {
                if (P_Validator.GetValidationRule(L_Control) != null)
                    AttachValidatorToControlValidation(L_Control, P_Validator);
            }
        }

        protected void AttachValidatorToControlValidation(Control c, DXValidationProvider v)
        {
            EventInfo L_EditValueChangedEvent = c.GetType().GetEvent("EditValueChanged");
            if (L_EditValueChangedEvent != null)
            {
                L_EditValueChangedEvent.AddEventHandler(c, (EventHandler)delegate(object sender, EventArgs e) { v.Validate(c); });
            }
        }

        protected void AddBindingCallback(object P_Control, Func<object, SendOrPostCallback> P_CallBack)
        {
            if (!_BindingCallbacks.ContainsKey(P_Control))
            {
                _BindingCallbacks.Add(P_Control, new List<Func<object, SendOrPostCallback>>());
                if (P_Control is Control)
                {
                    foreach (var L_Validator in _Validators)
                    {
                        if (L_Validator.GetValidationRule((Control)P_Control) != null)
                            AttachValidatorToControlValidation((Control)P_Control, L_Validator);
                    }
                }
            }
            _BindingCallbacks[P_Control].Add(P_CallBack);
        }

        public void ResetBindings(object P_Control)
        {
            if (_BindingCallbacks.ContainsKey(P_Control))
            {
                foreach (var callBack in _BindingCallbacks[P_Control])
                {
                    UIContext.ProtectUIDispatcher(callBack(P_Control));
                }
            }
        }

        public void SetCommand<TControl>(TControl P_Control,
            Expression<Func<TControl, bool>> P_Property,
            ICommand P_Command,
            object P_CommandDefaultParameter = null)
        {
            // get property info
            PropertyInfo P_SetPropertyDescriptor = ExpressionHelper.Reflect<TControl>.GetProperty(P_Property);

            // check in control property is editable
            if (P_SetPropertyDescriptor.CanWrite)
            {
                Func<object, SendOrPostCallback> L_InvokerFunctor = delegate(object c)
                {
                    return delegate(object state)
                    {
                        P_SetPropertyDescriptor.SetValue(c, P_Command.CanExecute(P_CommandDefaultParameter), null);
                    };
                };

                AddBindingCallback(P_Control, L_InvokerFunctor);

                // register in change manager service
                LokiServices.Event.RegisterCanExecuteChanged(P_Command, P_Control, delegate(TControl c, object sender, EventArgs e)
                {
                    UIContext.ProtectUIDispatcher(L_InvokerFunctor(c));
                });
            }
        }

        public void OneWay<TControl, TVM>(TControl P_Control,
            Expression<Func<TControl, object>> P_Property,
            TVM P_Bindable,
            Expression<Func<TVM, object>>[] P_BindableProperties,
            IMultiValueConverter P_Converter,
            object P_ValueConverterParameter = null)
            where TVM : INotifyPropertyChanged
        {
            // get property info
            PropertyInfo P_SetPropertyDescriptor = ExpressionHelper.Reflect<TControl>.GetProperty(P_Property);

            // check in control property is editable
            if (P_SetPropertyDescriptor.CanWrite)
            {
                // get source property name
                string[] L_BindableNames = P_BindableProperties.Select(x => ExpressionHelper.Reflect<TVM>.GetProperty(x).Name).ToArray();

                // get the destination property type
                Type L_DestType = P_SetPropertyDescriptor.PropertyType;

                // compile lambda getter
                Func<TVM, object>[] L_ValueGetters = P_BindableProperties.Select(x => x.Compile()).ToArray();
                Func<TVM, object[]> L_ArrayValueGetter = delegate(TVM v)
                {
                    object[] L_Return = new object[L_ValueGetters.Length];
                    for (int i = 0; i < L_ValueGetters.Length; i++)
                    {
                        L_Return[i] = L_ValueGetters[i](v);
                    }
                    return L_Return;
                };


                Func<object, SendOrPostCallback> L_InvokerFunctor = delegate(object c)
                {
                    return delegate(object state)
                    {
                        P_SetPropertyDescriptor.SetValue(c, P_Converter.Convert(L_ArrayValueGetter(P_Bindable), L_DestType, P_ValueConverterParameter, Application.CurrentCulture), null);
                    };
                };
                AddBindingCallback(P_Control, L_InvokerFunctor);

                foreach (var L_BindableProperty in P_BindableProperties)
                {
                    // register in change manager service
                    LokiServices.Event.RegisterPropertyChanged(P_Bindable, L_BindableProperty, P_Control, delegate(TControl c, object sender, PropertyChangedEventArgs e)
                    {
                        UIContext.ProtectUIDispatcher(L_InvokerFunctor(c));
                    });
                }
            }
        }

        public void OneWay<TControl, TVM>(TControl P_Control,
            Expression<Func<TControl, object>> P_Property,
            TVM P_Bindable,
            Expression<Func<TVM, object>> P_BindableProperty,
            IValueConverter P_Converter = null,
            object P_ValueConverterParameter = null)
            where TVM : INotifyPropertyChanged
        {
            // get property info
            PropertyInfo P_SetPropertyDescriptor = ExpressionHelper.Reflect<TControl>.GetProperty(P_Property);

            // check in control property is editable
            if (P_SetPropertyDescriptor.CanWrite)
            {
                // get source property name
                Func<object, SendOrPostCallback> L_InvokerFunctor = CreateValueToControlSetter<TVM>(P_Bindable, P_BindableProperty, P_Converter, P_ValueConverterParameter, P_SetPropertyDescriptor);

                AddBindingCallback(P_Control, L_InvokerFunctor);

                // register in change manager service
                LokiServices.Event.RegisterPropertyChanged(P_Bindable, P_BindableProperty, P_Control, delegate(TControl c, object sender, PropertyChangedEventArgs e)
                {
                    UIContext.ProtectUIDispatcher(L_InvokerFunctor(c));
                });
            }
        }

        private Func<object, SendOrPostCallback> CreateValueToControlSetter<TVM>(TVM P_Bindable, Expression<Func<TVM, object>> P_BindableProperty, IValueConverter P_Converter, object P_ValueConverterParameter, PropertyInfo P_SetPropertyDescriptor) where TVM : INotifyPropertyChanged
        {
            // compile lambda getter
            Func<TVM, object> L_ValueGetter = P_BindableProperty.Compile();

            Func<object, SendOrPostCallback> L_InvokerFunctor = delegate(object c)
            {
                return delegate(object state)
                {
                    if (P_Converter == null)
                        P_SetPropertyDescriptor.SetValue(c, L_ValueGetter(P_Bindable), null);
                    else
                        P_SetPropertyDescriptor.SetValue(c, P_Converter.Convert(L_ValueGetter(P_Bindable), P_SetPropertyDescriptor.PropertyType, P_ValueConverterParameter, Application.CurrentCulture), null);
                };
            };

            return L_InvokerFunctor;
        }

        public void TwoWay<TControl, TVM>(TControl P_Control,
            Expression<Func<TControl, object>> P_Property,
            TVM P_Bindable,
            Expression<Func<TVM, object>> P_BindableProperty,
            DataSourceUpdateMode P_Mode = DataSourceUpdateMode.OnValidation,
            IValueConverter P_Converter = null,
            object P_ValueConverterParameter = null)
            where TVM : INotifyPropertyChanged
        {
            // Get property descriptiors
            PropertyInfo L_DestPropertyDescriptor = ExpressionHelper.Reflect<TControl>.GetProperty(P_Property);
            PropertyInfo L_SourcePropertyDescriptor = ExpressionHelper.Reflect<TVM>.GetProperty(P_BindableProperty);

            // get source property name
            string L_BindableName = L_SourcePropertyDescriptor.Name;
            // get dest property name
            string L_Name = L_DestPropertyDescriptor.Name;

            // get the destination property type
            Type L_DestType = L_DestPropertyDescriptor.PropertyType;
            // get the source property type
            Type L_SourceType = L_SourcePropertyDescriptor.PropertyType;

            // check in control property is editable
            if (L_DestPropertyDescriptor.CanWrite)
            {
                Func<object, SendOrPostCallback> L_InvokerFunctor = CreateValueToControlSetter<TVM>(P_Bindable, P_BindableProperty, P_Converter, P_ValueConverterParameter, L_DestPropertyDescriptor);

                AddBindingCallback(P_Control, L_InvokerFunctor);

                // register in change manager service
                LokiServices.Event.RegisterPropertyChanged(P_Bindable, P_BindableProperty, P_Control, delegate(TControl c, object sender, PropertyChangedEventArgs e)
                {
                    UIContext.ProtectUIDispatcher(L_InvokerFunctor(c));
                });
            }

            if (L_SourcePropertyDescriptor.CanWrite && P_Mode != DataSourceUpdateMode.Never)
            {
                // wrap to catch property changed event
                NotifyPropertyChangedWrapper<TControl> L_Wrapper = new NotifyPropertyChangedWrapper<TControl>(P_Property);

                // select event to wrap witch DataSourceUpdateMode
                if (P_Mode == DataSourceUpdateMode.OnPropertyChanged)
                {
                    EventInfo L_PropertyChangedEvent = P_Control.GetType().GetEvent(L_Name + "Changed");
                    if (L_PropertyChangedEvent == null)
                        Log.ErrorFormat("Type {0} has not {1}Changed event", L_DestType, L_Name);
                    else
                    {
                        Delegate L_Handler = Delegate.CreateDelegate(L_PropertyChangedEvent.EventHandlerType, L_Wrapper, "EventBridge");
                        L_PropertyChangedEvent.AddEventHandler(P_Control, L_Handler);
                    }
                }
                else if (P_Mode == DataSourceUpdateMode.OnValidation)
                {
                    EventInfo L_PropertyChangedEvent = P_Control.GetType().GetEvent("Validated");
                    if (L_PropertyChangedEvent == null)
                        Log.ErrorFormat("Type {0} has not Validated event", L_DestType, L_Name);
                    else
                    {
                        Delegate L_Handler = Delegate.CreateDelegate(L_PropertyChangedEvent.EventHandlerType, L_Wrapper, "EventBridge");
                        L_PropertyChangedEvent.AddEventHandler(P_Control, L_Handler);
                    }
                }

                // compile lambda getter
                Func<TControl, object> L_ValueGetter = P_Property.Compile();

                LokiServices.Event.RegisterPropertyChanged(L_Wrapper, L_Name, P_Bindable, delegate(TVM c, object s, PropertyChangedEventArgs e)
                {
                    if (P_Converter == null)
                        L_SourcePropertyDescriptor.SetValue(c, L_ValueGetter(P_Control), null);
                    else
                        L_SourcePropertyDescriptor.SetValue(c, P_Converter.ConvertBack(L_ValueGetter(P_Control), L_SourceType, P_ValueConverterParameter, Application.CurrentCulture), null);
                });
            }
        }

        #region IDisposable
        /// <summary>
        /// Releases all resources used by an instance of the <see cref="Databinder" /> class.
        /// </summary>
        /// <remarks>
        /// This method calls the virtual <see cref="Dispose(bool)" /> method, passing in <strong>true</strong>, and then suppresses 
        /// finalization of the instance.
        /// </remarks>
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        /// <summary>
        /// Releases unmanaged resources before an instance of the <see cref="Databinder" /> class is reclaimed by garbage collection.
        /// </summary>
        /// <remarks>
        /// This method releases unmanaged resources by calling the virtual <see cref="Dispose(bool)" /> method, passing in <strong>false</strong>.
        /// </remarks>
        ~Databinder()
        {
            Dispose(false);
        }

        /// <summary>
        /// Releases the unmanaged resources used by an instance of the <see cref="Databinder" /> class and optionally releases the managed resources.
        /// </summary>
        /// <param name="disposing"><strong>true</strong> to release both managed and unmanaged resources; <strong>false</strong> to release only unmanaged resources.</param>
        protected virtual void Dispose(bool disposing)
        {
            ClearBindings();
        }
        #endregion
    }


}
